package tarantula.instrumenter;

import java.util.LinkedList;
import java.util.List;

import plume.Option;
import plume.Options;


import soot.G;
import soot.Pack;
import soot.PackManager;
import soot.Scene;
import soot.SootClass;
import soot.Transform;
import tarantula.util.Globals;

public class Main {

	@Option("The input dir of all transforming classes")
	public static String input_dir = null;

	@Option("The output dir for all transformed classes")
	public static String output_dir = null;
	
	@Option("The output dir for all junit classes")
	public static String junit_input_dir = null;
	
	@Option("The output dir for all transformed junit classes")
	public static String junit_output_dir = null;

	@Option("A file containing all classes prefix that should be skipped")
	public static String skip_class_prefix = null;
	
	@Option("Output format format, the same as Soot's")
	public static String output_format = "c"; //or "J"
	
	@Option("The additoinal classpath used in instrumentation.")
	public static String additional_classpath = null;
	
	@Option("The subject program package to be instrumented.")
	public static String include_subject_package = null;
	
	@Option("The JUnit package to be instrumented.")
	public static String include_junit_package = null;
	
	@Option("Show all the help options")
	public static boolean help = false;

	private void instrument_subject() {
		/* add a phase to transformer pack by call Pack.add */
		Pack jtp = PackManager.v().getPack("jtp");
		jtp.add(new Transform("jtp.instrumenter", new EveryStatementIntrumenter()));
		
		/* Give control to Soot to process all options,
		 * InvokeStaticInstrumenter.internalTransform will get called. */
		List<String> sootArgs = new LinkedList<String>();
		
		sootArgs.add("-prepend-classpath");
		sootArgs.add("-keep-line-number");
		sootArgs.add("-soot-class-path");
		sootArgs.add(input_dir + Globals.pathSep + Globals.getClassPath()
				+ (additional_classpath == null ? "" : (Globals.pathSep + additional_classpath)));
		//System.out.println("." + Globals.pathSep + input_dir + Globals.pathSep + Globals.getClassPath());
		sootArgs.add("-output-format");
		sootArgs.add(output_format);
		if (include_subject_package != null) {
			sootArgs.add("-i");
			sootArgs.add(include_subject_package);
		}
		sootArgs.add("-process-dir");
		sootArgs.add(input_dir);
		sootArgs.add("-output-dir");
		sootArgs.add(output_dir);
		
		System.out.println("Executing soot args: " + sootArgs);
		
		Scene.v().addBasicClass("tarantula.instrumenter.Tracer", SootClass.SIGNATURES);
		soot.Main.main(sootArgs.toArray(new String[0]));
	}
	
	private void instrument_junit() {
		/* add a phase to transformer pack by call Pack.add */
		Pack jtp = PackManager.v().getPack("jtp");
		jtp.add(new Transform("jtp.junitinstrumenter", new JUnitTestInstrumenter()));

		// call the instrumenter
		List<String> sootArgs = new LinkedList<String>();
		sootArgs.add("-prepend-classpath");
		sootArgs.add("-keep-line-number");
		sootArgs.add("-soot-class-path");
		sootArgs.add("." + Globals.pathSep +
				(additional_classpath == null ? "" : (Globals.pathSep + additional_classpath)) +
				Globals.pathSep + junit_input_dir + Globals.pathSep + Globals.getClassPath());
		sootArgs.add("-output-format");
		sootArgs.add(output_format);
		if (include_junit_package != null) {
			sootArgs.add("-i");
			sootArgs.add(include_junit_package);
		}
		sootArgs.add("-process-dir");
		sootArgs.add(junit_input_dir);
		sootArgs.add("-output-dir");
		sootArgs.add(junit_output_dir);

		System.out.println("Executing soot args: " + sootArgs);
		Scene.v().addBasicClass("tarantula.instrumenter.Tracer", SootClass.SIGNATURES);
		soot.Main.main(sootArgs.toArray(new String[0]));
	}
	
	public static void main(String[] args) {
		parse_check_and_set_args(args);
		Main main = new Main();
		main.instrument_subject();
		G.v().reset(); //must reset soot's environment
		main.instrument_junit();
		G.v().reset(); //must reset soot's environment
	}
	
	private static void parse_check_and_set_args(String[] args) {
		//process the arguments
		Options options = new Options("TarantuaJ options: ", Main.class);
		String[] file_args = options.parse_or_usage(args);
		if(file_args.length != 0) {
			for(String errorMsg : file_args) {
				System.out.println(errorMsg);
				System.exit(1);
			}
		}
		
		//show the welcome page
		if(Main.help) {
			System.out.println("Welcome to use TarantuaJ. It has been installed!");
			System.out.println();
			options.print_usage();
			System.exit(1);
		}
		
		if(Main.input_dir == null) {
			System.out.println("The input dir of all classes to be transformed can not be null!");
			System.out.println();
			options.print_usage();
			System.exit(1);
		}
		
		if(Main.output_dir == null) {
			System.out.println("The output dir for all transformed classes can not be null!");
			System.out.println();
			options.print_usage();
			System.exit(1);
		}
		
		if(Main.junit_input_dir == null) {
			System.out.println("The input dir for all junit tests can not be null!");
			System.out.println();
			options.print_usage();
			System.exit(1);
		}
		
		if(Main.junit_output_dir == null) {
			System.out.println("The output dir for all junit tests can not be null!");
			System.out.println();
			options.print_usage();
			System.exit(1);
		}
	}

}